package mobi.weq.util.edinburgh;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import mobi.weq.model.SensorObservation;
import mobi.weq.util.Util;

public class EdinburghGeneratorUtil {

	public static void main(String[] args) {
		double increment = 1.0 / EdinburghMap.GRID_DENSITY;
		System.out.println(increment);
		for (double lat = EdinburghMap.LAT1; lat < EdinburghMap.LAT2 + increment; lat += increment) {
			// System.out.println(lat);
		}
		System.out.println("------------------------");
		for (double lng = EdinburghMap.LNG1; lng < EdinburghMap.LNG2 + increment; lng += increment) {
			System.out.println(lng);
		}

		// countRandomLocation();
		// System.out.println(maxSize());
		// ArrayList<SensorObservation> o = EdinburghGeneratorUtil
		// .generateEdinburghSensorHourlyObservationsThroughoutCity(1);
		// System.out.println(o.size() / 24);

		// printEdinburghStatistics("co");
		// printEdinburghStatistics("o3");
		// printEdinburghStatistics("no2");
		// printEdinburghStatistics("pm10");
		// printEdinburghStatistics("pm2p5");
		// printEdinburghStatistics("so2");
		// int time = new Date().getHours();
		// for (int i = 0; i < 60; i++) {
		// time = ((time - 1) + 24) % 24;
		// System.out.println(time);
		// }
		// generateEdinburghObservations(4);
		// System.out.println(generateEdinburghLatitude());

	}

	public static ArrayList<SensorObservation> generateEdinburghSensorObservations(int lastNDays) {
		Date d = new Date();
		d.setMinutes(0);
		d.setSeconds(0);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		int total = lastNDays * 24;
		int time = d.getHours();
		for (int i = 0; i < total; i++) {
			SensorObservation o = generateEdinburghSensorObservation(d);
			observations.add(o);
			d = Util.decreaseDateByHour(d, 1);
		}
		return observations;
	}

	public static boolean isObservationFromSameGrid(SensorObservation o1, SensorObservation o2) {
		return (o1.getLatitude() == o2.getLatitude()) && (o1.getLongitude() == o2.getLongitude());
	}

	public static SensorObservation mergeSensorObservation(SensorObservation o1, SensorObservation o2) {
		SensorObservation o = new SensorObservation();
		o.setCity(o1.getCity());
		o.setNo2((o1.getPm2p5() + o2.getPm2p5()) / 2);
		o.setO3((o1.getPm2p5() + o2.getPm2p5()) / 2);
		o.setPm10((o1.getPm2p5() + o2.getPm2p5()) / 2);
		o.setPm2p5((o1.getPm2p5() + o2.getPm2p5()) / 2);
		o.setSo2((o1.getPm2p5() + o2.getPm2p5()) / 2);
		o.setTimestamp(o1.getTimestamp());
		o.setLongitude(o1.getLongitude());
		o.setLatitude(o1.getLatitude());
		return o;
	}

	public static ArrayList<SensorObservation> generateEdinburghSensorHourlyObservationsThroughoutCity(int lastNDays) {
		Date d = new Date();
		d.setMinutes(0);
		d.setSeconds(0);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		int total = lastNDays * 24;
		int time = d.getHours();

		for (int i = 0; i < total; i++) {
			Map<String, SensorObservation> map = new HashMap<String, SensorObservation>();
			for (int j = 0; j < 100000; j++) {
				SensorObservation o = generateEdinburghSensorObservation(d);
				// 10000 for 10 mts, 1000 for 100 mtrs and 100 for 1 km
				// 40 million grids for 10 mtr grid
				//

				double latitude = Math.floor(o.getLatitude() * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				double longitude = Math.floor(o.getLongitude() * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				o.setLatitude(latitude);
				o.setLongitude(longitude);
				String locationId = "LAT" + latitude + "#LNG" + longitude;
				if (map.containsKey(locationId)) {
					SensorObservation t = map.get(locationId);
					if (isObservationFromSameGrid(o, t)) {
						o = mergeSensorObservation(o, t);
					}
				}
				map.put(locationId, o);
			}
			observations.addAll(map.values());
			d = Util.decreaseDateByHour(d, 1);
		}
		return observations;
	}

	public static SensorObservation generateEdinburghSensorObservation(Date d) {
		SensorObservation o = new SensorObservation();
		int time = d.getHours();
		o.setTimestamp(d);
		o.setCity("Edinburgh");
		o.setO3(edinburghO3Generator(time));
		o.setPm10(edinburghPM10Generator(time));
		o.setPm2p5(edinburghPM2P5Generator(time));
		o.setSo2(edinburghSO2Generator(time));
		o.setNo2(edinburghNO2Generator(time));
		o.setLatitude(generateEdinburghLatitude());
		o.setLongitude(generateEdinburghLongitude());
		return o;
	}

	public static double edinburghNO2Generator(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.no2Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.no2Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	public static double edinburghSO2Generator(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.so2Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.so2Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	public static double edinburghO3Generator(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.o3Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.o3Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	public static double edinburghPM10Generator(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.pm10Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.pm10Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	public static double edinburghPM2P5Generator(int time) {
		Random random = new Random();
		Double mean = EdinburghPollutionSubstanceStatistics.pm2p5Map.get(time).get(0);
		Double variance = EdinburghPollutionSubstanceStatistics.pm2p5Map.get(time).get(1);
		return mean + random.nextGaussian() * variance;
	}

	public static double generateEdinburghLatitude() {
		double diff = EdinburghMap.LAT2 - EdinburghMap.LAT1;
		Random r = new Random();
		return r.nextDouble() * diff + EdinburghMap.LAT1;
	}

	public static double generateEdinburghLongitude() {

		double diff = EdinburghMap.LNG2 - EdinburghMap.LNG1;
		Random r = new Random();
		return r.nextDouble() * diff + EdinburghMap.LNG1;
	}

	public static SensorObservation generateEdinburghCurrentObservation() {
		return generateEdinburghSensorObservation(new Date());
	}

	/****************************/
	public static String getLocationId() {
		double num = EdinburghMap.GRID_DENSITY;
		double latitude = Math.floor(generateEdinburghLatitude() * num) / num;
		double longitude = Math.floor(generateEdinburghLongitude() * num) / num;
		String locationId = "LAT" + latitude + "#LNG" + longitude;
		return locationId;
	}

	public static SortedSet<Double> getLocationLatitudeSet(int num) {
		SortedSet<Double> set = new TreeSet<Double>();
		for (int i = 0; i < 1000000; i++) {
			double latitude = Math.floor(generateEdinburghLatitude() * num) / num;
			set.add(latitude);
		}
		System.out.println(set.size());
		return set;
	}

	public static SortedSet<Double> getLocationLongitudeSet(int num) {
		SortedSet<Double> set = new TreeSet<Double>();
		for (int i = 0; i < 1000000; i++) {
			double longitude = Math.floor(generateEdinburghLongitude() * num) / num;
			set.add(longitude);
		}
		System.out.println(set.size());
		return set;
	}

	public static long maxSize() {
		long latDiff = (long) (EdinburghMap.LAT2 * EdinburghMap.GRID_DENSITY) - (long) (EdinburghMap.LAT1 * EdinburghMap.GRID_DENSITY);
		long lngDiff = (long) (EdinburghMap.LNG2 * EdinburghMap.GRID_DENSITY) - (long) (EdinburghMap.LNG1 * EdinburghMap.GRID_DENSITY);
		latDiff++;
		lngDiff++;
		System.out.println(latDiff + "," + lngDiff);
		return (latDiff) * lngDiff;
	}

	public static void countRandomLocation() {
		int number = 100;
		System.out.println("Max Size " + maxSize());

		Map<String, String> countMap = new HashMap<String, String>();
		for (int i = 0; i < 1000000; i++) {
			countMap.put(getLocationId(), null);
		}

		System.out.println("Map Size " + countMap.size());
		SortedSet<Double> set = getLocationLatitudeSet(number);
		SortedSet<Double> set2 = getLocationLongitudeSet(number);
		System.out.println(set);
		System.out.println(set2);

		// System.out.println(getLocationLatitude(num));
	}

	public static ArrayList<SensorObservation> generateEdinburghSensorHourlyObservationsThroughoutCityEveryGrid(Date d) {
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		int day = d.getDate();
		double increment = 1.0 / EdinburghMap.GRID_DENSITY;
		for (; day == d.getDate();) {
			for (double lat = EdinburghMap.LAT1; lat < EdinburghMap.LAT2 + increment; lat += increment) {
				for (double lng = EdinburghMap.LNG1; lng < EdinburghMap.LNG2 + increment; lng += increment) {
					SensorObservation o = generateEdinburghSensorObservation(d);
					double latitude = Math.floor(lat * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
					double longitude = Math.floor(lng * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
					o.setLatitude(latitude);
					o.setLongitude(longitude);
					observations.add(o);
				}
			}
			d = Util.decreaseDateByHour(d, 1);
		}
		return observations;
	}

	public static ArrayList<SensorObservation> generateEdinburghSensorHourlyObservationsThroughoutCityEveryGridForCurrentHour() {
		Date d = new Date();
		d.setMinutes(0);
		ArrayList<SensorObservation> observations = new ArrayList<SensorObservation>();
		double increment = 1.0 / EdinburghMap.GRID_DENSITY;
		for (double lat = EdinburghMap.LAT1; lat < EdinburghMap.LAT2 + increment; lat += increment) {
			for (double lng = EdinburghMap.LNG1; lng < EdinburghMap.LNG2 + increment; lng += increment) {
				SensorObservation o = generateEdinburghSensorObservation(d);
				double latitude = Math.floor(lat * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				double longitude = Math.floor(lng * EdinburghMap.GRID_DENSITY) / EdinburghMap.GRID_DENSITY;
				o.setLatitude(latitude);
				o.setLongitude(longitude);
				observations.add(o);
			}
		}
		return observations;
	}
}
